<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="fUQvB" id="fUQvB"><span data-lake-id="u28f48ee5" id="u28f48ee5">典型回答</span></h1>
  <p data-lake-id="ufffaa744" id="ufffaa744"><br></p>
  <p data-lake-id="u8edbc249" id="u8edbc249"><span data-lake-id="u75f2ceaa" id="u75f2ceaa">select, poll, 和 epoll 都是 Linux 中常见的 I/O 多路复用技术，它们可以用于同时监听多个文件描述符（file descriptor，后文简称fd），当任意一个文件描述符就绪时，就能够非阻塞的读写数据。</span></p>
  <p data-lake-id="ufa7bedb3" id="ufa7bedb3"><br></p>
  <ul list="ud5dd09a8">
   <li fid="uf34e608c" data-lake-id="u65c46d2e" id="u65c46d2e"><span data-lake-id="uf600cd95" id="uf600cd95">select 是最原始的 I/O 多路复用技术，它的缺点是最多只能监听 1024 个文件描述符。</span></li>
   <li fid="uf34e608c" data-lake-id="u01674eb3" id="u01674eb3"><span data-lake-id="u0e1299e8" id="u0e1299e8">poll</span><span data-lake-id="uc33b5287" id="uc33b5287"> 在 </span><span data-lake-id="uc8a8ac4a" id="uc8a8ac4a">select</span><span data-lake-id="uead76424" id="uead76424"> 的基础上增加了支持监听更多的文件描述符的能力，但是复杂度随着监听的文件描述符数量的增加而增加。</span></li>
   <li fid="uf34e608c" data-lake-id="uc7fb7419" id="uc7fb7419"><span data-lake-id="u822b6184" id="u822b6184">epoll 在 poll 的基础上进一步优化了复杂度，可以支持更多的文件描述符，并且具有更高的效率。</span></li>
  </ul>
  <h2 data-lake-id="EIonK" id="EIonK"><span data-lake-id="u49801008" id="u49801008" style="color: rgb(33, 37, 41)">select</span></h2>
  <p data-lake-id="u8d9f64f4" id="u8d9f64f4"><span data-lake-id="u0b116039" id="u0b116039" class="lake-fontsize-12">函数签名如下：</span></p>
  <pre lang="java"><code>
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
</code></pre>
  <p data-lake-id="uf99e4df8" id="uf99e4df8"><span data-lake-id="u501552e7" id="u501552e7" class="lake-fontsize-12">select函数可以监听read，write，except的fd。当select返回后，可以遍历对应的fd_set来寻找就绪的fd，从而进行业务处理</span></p>
  <p data-lake-id="ue153a453" id="ue153a453"><span data-lake-id="u01d44da5" id="u01d44da5" class="lake-fontsize-12">​</span><br></p>
  <p data-lake-id="u209f1d05" id="u209f1d05"><strong><span data-lake-id="u5796f186" id="u5796f186" class="lake-fontsize-12">select诞生比较早，几乎在所有的平台中都支持。但是select有个缺点就是</span></strong><strong><span data-lake-id="ubc402de2" id="ubc402de2" class="lake-fontsize-12" style="color: rgb(33, 37, 41)">单个进程能够监视的文件描述符的数量存在最大限制，在Linux上一般为1024，可以通过修改宏定义甚至重新编译内核的方式提升这一限制，但是这样也会造成效率的降低。</span></strong></p>
  <p data-lake-id="uf6d91fc6" id="uf6d91fc6"><span data-lake-id="uc5dd0e56" id="uc5dd0e56" class="lake-fontsize-12" style="color: rgb(33, 37, 41)">​</span><br></p>
  <p data-lake-id="uf9fc773f" id="uf9fc773f"><span data-lake-id="ub65c3e05" id="ub65c3e05" class="lake-fontsize-12" style="color: rgb(33, 37, 41)">除此之外，</span><span data-lake-id="u53f7c8fa" id="u53f7c8fa" class="lake-fontsize-12" style="color: rgb(18, 18, 18)">包含大量fd的数组被整体复制于用户态和内核的地址空间之间，而不论这些文件描述符是否就绪，其开销也随着文件描述符数量增加而线性增大。</span></p>
  <h2 data-lake-id="uVujv" id="uVujv"><span data-lake-id="u2025aa0f" id="u2025aa0f">poll</span></h2>
  <p data-lake-id="u8be80e6c" id="u8be80e6c"><span data-lake-id="uf2236367" id="uf2236367" class="lake-fontsize-12">函数签名如下所示：</span></p>
  <p data-lake-id="u2ff5510d" id="u2ff5510d"><span data-lake-id="u3bf1cf1a" id="u3bf1cf1a" class="lake-fontsize-12">​</span><br></p>
  <pre lang="java"><code>
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
};
</code></pre>
  <p data-lake-id="u6ae574aa" id="u6ae574aa"><span data-lake-id="u396764b6" id="u396764b6" class="lake-fontsize-12">​</span><br></p>
  <p data-lake-id="u17336d76" id="u17336d76"><span data-lake-id="uf535afb0" id="uf535afb0" class="lake-fontsize-12">同select一样，poll返回后，也是需要轮询pollfd来获取就绪的fd。不仅如此，所有的fds也是在内核态和用户态中来回切换，也会影响效率。</span></p>
  <p data-lake-id="u885736f5" id="u885736f5"><span data-lake-id="uabea6129" id="uabea6129" class="lake-fontsize-12">​</span><br></p>
  <p data-lake-id="ud403401f" id="ud403401f"><span data-lake-id="u502ce1d1" id="u502ce1d1" class="lake-fontsize-12">但是因为fds基于链表，所以就没有了最长1024的限制。</span></p>
  <h2 data-lake-id="qFgcG" id="qFgcG"><span data-lake-id="u7bb660ae" id="u7bb660ae">epoll</span></h2>
  <p data-lake-id="u44647b22" id="u44647b22"><span data-lake-id="ufa18c7e4" id="ufa18c7e4" class="lake-fontsize-12">epoll基于Linux2.4.5，函数签名如下：</span></p>
  <pre lang="java"><code>
// 创建一个epoll的句柄，size用来告诉内核这个监听的数目一共有多大
int epoll_create(int size)；
// 注册要监听的事件类型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)；
// 等待事件发生
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
</code></pre>
  <p data-lake-id="u5aa35c58" id="u5aa35c58" style="text-align: left"><span data-lake-id="ud3486147" id="ud3486147" class="lake-fontsize-12" style="color: rgb(0, 0, 0)">​</span><br></p>
  <p data-lake-id="ub7cf0117" id="ub7cf0117" style="text-align: left"><span data-lake-id="udfb599a3" id="udfb599a3" class="lake-fontsize-12" style="color: rgb(0, 0, 0)">每次注册新的事件调用epoll_ctl时，epoll会把所有的fd拷贝进内核，而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。</span></p>
  <p data-lake-id="ua7b5f613" id="ua7b5f613" style="text-align: left"><span data-lake-id="uf5eccbdf" id="uf5eccbdf" class="lake-fontsize-12" style="color: rgb(0, 0, 0)">​</span><br></p>
  <p data-lake-id="uf9eea8c3" id="uf9eea8c3" style="text-align: left"><span data-lake-id="u43871399" id="u43871399" class="lake-fontsize-12" style="color: rgb(0, 0, 0)">同时，epoll会通过epoll_wait查看是否有就绪的fd，如果有就绪的fd，就会直接使用（O(1)）。而不是像之前两个一样，每次需要手动遍历才能得到就绪的fd（O(n)）</span></p>
  <p data-lake-id="uf466a52d" id="uf466a52d" style="text-align: left"><span data-lake-id="u434d133a" id="u434d133a" class="lake-fontsize-12" style="color: rgb(0, 0, 0)">​</span><br></p>
  <p data-lake-id="ud389481b" id="ud389481b" style="text-align: left"><span data-lake-id="u2e06eb6f" id="u2e06eb6f" class="lake-fontsize-12" style="color: rgb(0, 0, 0)">除此之外，它所支持的fd上限是最大可以打开文件的数目，这个数字一般远大于2048，举个例子，在1GB内存的机器上大约是10万左右，具体数目可以cat /proc/sys/fs/file-max查看，一般来说这个数目和系统内存关系很大。</span></p>
  <p data-lake-id="u8300cb61" id="u8300cb61" style="text-align: left"><span data-lake-id="uc486f91b" id="uc486f91b" class="lake-fontsize-12" style="color: rgb(0, 0, 0)">​</span><br></p>
  <h1 data-lake-id="SoUGY" id="SoUGY"><span data-lake-id="uc0e1dc5e" id="uc0e1dc5e">知识扩展</span></h1>
  <h2 data-lake-id="POSSu" id="POSSu"><span data-lake-id="u9d0699cf" id="u9d0699cf">三者的主要区别是啥？</span></h2>
  <table data-lake-id="yodii" id="yodii" margin="true" width-mode="contain" class="lake-table" style="width: 753px">
   <colgroup>
    <col width="106">
    <col width="133">
    <col width="250">
    <col width="264">
   </colgroup>
   <tbody>
    <tr data-lake-id="u9c464ff4" id="u9c464ff4">
     <td data-lake-id="uacc1ee72" id="uacc1ee72"></td>
     <td data-lake-id="u4fe43bb0" id="u4fe43bb0">
      <p data-lake-id="u0d638e79" id="u0d638e79"><span data-lake-id="u2483bd35" id="u2483bd35">fd长度</span></p></td>
     <td data-lake-id="u111b0092" id="u111b0092">
      <p data-lake-id="u4719236a" id="u4719236a"><span data-lake-id="udd2f6637" id="udd2f6637">遍历所有fd</span></p></td>
     <td data-lake-id="ua4690628" id="ua4690628">
      <p data-lake-id="u5152f4e0" id="u5152f4e0"><span data-lake-id="u67a0b565" id="u67a0b565">把fd从用户态copy到内核态</span></p></td>
    </tr>
    <tr data-lake-id="uac4c3099" id="uac4c3099">
     <td data-lake-id="u1ade42c8" id="u1ade42c8">
      <p data-lake-id="u09a7e0b1" id="u09a7e0b1"><span data-lake-id="u403a1805" id="u403a1805">select</span></p></td>
     <td data-lake-id="uee0534c1" id="uee0534c1">
      <p data-lake-id="ua8631a60" id="ua8631a60"><span data-lake-id="u4232d17e" id="u4232d17e">1024</span></p></td>
     <td data-lake-id="u26bf05db" id="u26bf05db">
      <p data-lake-id="uaefc5fb3" id="uaefc5fb3"><span data-lake-id="u93703a0d" id="u93703a0d">是</span></p></td>
     <td data-lake-id="ud9ed8703" id="ud9ed8703">
      <p data-lake-id="u1d670ab2" id="u1d670ab2"><span data-lake-id="uf190a45c" id="uf190a45c">是</span></p></td>
    </tr>
    <tr data-lake-id="u9ea03f26" id="u9ea03f26">
     <td data-lake-id="ua8d533df" id="ua8d533df">
      <p data-lake-id="u2873c3bc" id="u2873c3bc"><span data-lake-id="ue99b58a4" id="ue99b58a4">poll</span></p></td>
     <td data-lake-id="u23fa12d3" id="u23fa12d3">
      <p data-lake-id="u63a6f94a" id="u63a6f94a"><span data-lake-id="u967dd809" id="u967dd809">无限制</span></p></td>
     <td data-lake-id="u984b2443" id="u984b2443">
      <p data-lake-id="ud6827361" id="ud6827361"><span data-lake-id="uad76bd5c" id="uad76bd5c">是</span></p></td>
     <td data-lake-id="u368f7784" id="u368f7784">
      <p data-lake-id="ud4f52f8b" id="ud4f52f8b"><span data-lake-id="uf744f449" id="uf744f449">是</span></p></td>
    </tr>
    <tr data-lake-id="u56bd1b14" id="u56bd1b14">
     <td data-lake-id="u0a762948" id="u0a762948">
      <p data-lake-id="udf3c7bc1" id="udf3c7bc1"><span data-lake-id="u216fa1dd" id="u216fa1dd">epoll</span></p></td>
     <td data-lake-id="ub2741c84" id="ub2741c84">
      <p data-lake-id="ufd7c995d" id="ufd7c995d"><span data-lake-id="uda786424" id="uda786424">无限制</span></p></td>
     <td data-lake-id="ub039945e" id="ub039945e">
      <p data-lake-id="u4d1f0878" id="u4d1f0878"><span data-lake-id="u9c61dddf" id="u9c61dddf">否</span></p></td>
     <td data-lake-id="uc3d90a31" id="uc3d90a31">
      <p data-lake-id="u301e35db" id="u301e35db"><span data-lake-id="u72639f82" id="u72639f82">否</span></p></td>
    </tr>
   </tbody>
  </table>
  <h2 data-lake-id="W2skh" id="W2skh"><span data-lake-id="u69c85676" id="u69c85676">epoll的两种模式是啥？</span></h2>
  <p data-lake-id="u12e309c7" id="u12e309c7"><br></p>
  <p data-lake-id="u27c9440b" id="u27c9440b"><span data-lake-id="ub4ca5318" id="ub4ca5318" class="lake-fontsize-12">我们知道epoll是通过epoll_wait来获取就绪的fd，那么如果就绪的fd一直没有被消费，该如何处理呢？这就又了两种模式。</span><span data-lake-id="u803b7701" id="u803b7701" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">LT（level trigger）和ET（edge trigger）。LT模式是默认模式，LT模式与ET模式的区别如下： </span></p>
  <p data-lake-id="ub99d82c6" id="ub99d82c6"><span data-lake-id="uc76062ea" id="uc76062ea" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <ol list="u4b742879">
   <li fid="u1661b7f3" data-lake-id="u67b37a43" id="u67b37a43"><span data-lake-id="u5e3ae0de" id="u5e3ae0de" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">LT模式：当epoll_wait检测到描述符事件发生并将此事件通知应用程序，应用程序可以不立即处理该事件。下次调用epoll_wait时，会再次响应应用程序并通知此事件</span></li>
   <li fid="u1661b7f3" data-lake-id="u33892e2d" id="u33892e2d"><span data-lake-id="u59a1f450" id="u59a1f450" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">ET模式：当epoll_wait检测到描述符事件发生并将此事件通知应用程序，应用程序必须立即处理该事件。如果不处理，下次调用epoll_wait时，不会再次响应应用程序并通知此事件。</span></li>
  </ol>
  <p data-lake-id="u551f59bc" id="u551f59bc"><span data-lake-id="ua4807377" id="ua4807377" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u265a2f1c" id="u265a2f1c"><span data-lake-id="u5e7ea2e1" id="u5e7ea2e1" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">因为ET模式在很大程度上减少了epoll事件被重复触发的次数，因此效率要比LT模式高。epoll工作在ET模式的时候，必须使用非阻塞socket，以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。</span></p>
 </body>
</html>